/*
 * @(#)XfdmFixedClustering.java        1.0 2000/05/09
 *
 * This file is part of Xfuzzy 3.0, a design environment for fuzzy logic
 * based systems.
 *
 * (c) 2000 IMSE-CNM. The authors may be contacted by the email address:
 *                    xfuzzy-team@imse.cnm.es
 *
 * Xfuzzy is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation.
 *
 * Xfuzzy is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

package xfuzzy.xfdm.model.algorithm;

import xfuzzy.lang.*;
import xfuzzy.xfdm.model.*;
import xfuzzy.xfds.XfdsDataSet;
import xfuzzy.xfsl.model.*;

/**
 * Algoritmos de clustering fijo
 * 
 * @author Francisco Jose Moreno Velo
 *
 */
public class XfdmFixedClustering extends XfdmAlgorithm {

	//----------------------------------------------------------------------------//
	//                            CONSTANTES PUBLICAS                             //
	//----------------------------------------------------------------------------//

	/**
	 * C�digo que identifica el algoritmo Hard C-means
	 */
	public static final int HARD_CMEANS = 0;
	
	/**
	 * C�digo que identifica el algoritmo C-means
	 */
	public static final int CMEANS = 1;
	
	/**
	 * C�digo que identifica el algoritmo Gustafson-Kessel
	 */
	public static final int GUSTAFSON_KESSEL = 2;
	
	/**
	 * C�digo que identifica el algoritmo Gath-Geva
	 */
	public static final int GATH_GEVA = 3;

	//----------------------------------------------------------------------------//
	//                            MIEMBROS PRIVADOS                               //
	//----------------------------------------------------------------------------//

	/**
	 * Tipo de clustering
	 */
	private int clustering;
	
	/**
	 * N�mero de clusters
	 */
	private int num_clusters;
	
	/**
	 * N�mero m�ximo de iteraciones
	 */
	private int num_iterations;
	
	/**
	 * �ndice de borrosidad
	 */
	private double fuzziness;
	
	/**
	 * Minimo grado de variacion
	 */
	private double epsilon;
	
	/**
	 * Opci�n de aprendizaje
	 */
	private boolean learning;

	/**
	 * Tama�o del cluster (n�mero de variables de entraqda y salida)
	 */
	private int width;
	
	/**
	 * Conjunto de patrones normalizado
	 */
	private double point[][];
	
	/**
	 * Conjunto de clusters
	 */
	private double cluster[][];
	
	/**
	 * Grado de pertenencia de cada punto a cada cluster
	 */
	private double degree[][];
	
	/**
	 * Grado de pertenencia de cada punto a cada cluster en la iteraci�n previa
	 */
	private double old_degree[][];

	//----------------------------------------------------------------------------//
	//                                CONSTRUCTOR                                 //
	//----------------------------------------------------------------------------//

	/**
	 * Constructor por defecto
	 */
	public XfdmFixedClustering() {
		this.clustering = HARD_CMEANS;
		this.num_clusters = 10;
		this.num_iterations = 10;
		this.fuzziness = 2.0;
		this.epsilon = 0.01;
		this.learning = false;
	}

	/**
	 * Constructor desde la interfaz gr�fica
	 */
	public XfdmFixedClustering(int algorithm, int clusters, int iterations,
			double fuzziness, double epsilon, boolean lrn) {
		this.clustering = algorithm;
		this.num_clusters = clusters;
		this.num_iterations = iterations;
		this.fuzziness = fuzziness;
		this.epsilon = epsilon;
		this.learning = lrn;
	}

	/**
	 * Constructor desde el fichero de configuraci�n
	 */
	public XfdmFixedClustering(int algorithm, double[] param) {
		this.clustering = algorithm;
		this.num_clusters = (int) param[0];
		this.num_iterations = (int) param[1];
		this.fuzziness = param[2];
		this.epsilon = param[3];
		this.learning = (((int) param[4]) == 1);
	}

	//----------------------------------------------------------------------------//
	//                             M�TODOS P�BLICOS                               //
	//----------------------------------------------------------------------------//

	//----------------------------------------------------------------------------//
	// M�todos de acceso a la configuraci�n	                                      //
	//----------------------------------------------------------------------------//

	/**
	 * Obtiene el c�digo del algoritmo de clustering elegido
	 */
	public int getClustering() {
		return this.clustering;
	}

	/**
	 * Asigna el c�digo del algoritmo de clustering	
	 */
	public void setClustering(int algorithm) {
		this.clustering = algorithm;
	}

	/**
	 * Obtiene el valor del n�mero de clusters elegido
	 */
	public int getNumberOfClusters() {
		return this.num_clusters;
	}

	/**
	 * Asigna el valor del n�mero de clusters
	 */
	public void setNumberOfClusters(int num) {
		this.num_clusters = num;
	}

	/**
	 * Obtiene el valor del �ndice de borrosidad
	 */
	public double getFuzziness() {
		return this.fuzziness;
	}

	/**
	 * Asigna el valor del �ndice de borrosidad
	 */
	public void setFuzziness(double fuzz) {
		this.fuzziness = fuzz;
	}

	/**
	 * Obtiene el valor del n�mero m�ximo de iteraciones
	 */
	public int getNumberOfIterations() {
		return this.num_iterations;
	}

	/**
	 * Asigna el valor del n�mero m�ximo de iteraciones
	 */
	public void setNumberOfIterations(int num) {
		this.num_iterations = num;
	}

	/**
	 * Obtiene el valor del m�nimo grado de variaci�n
	 */
	public double getEpsilon() {
		return this.epsilon;
	}

	/**
	 * Asigna el valor del m�nimo grado de variaci�n
	 */
	public void setEpsilon(double degree) {
		this.epsilon = degree;
	}

	/**
	 * Obtiene el valor de la opci�n de aprendizaje
	 */
	public boolean getLearning() {
		return this.learning;
	}

	/**
	 * Asigna el valor de la opci�n de aprendizaje
	 */
	public void setLearning(boolean lrn) {
		this.learning = lrn;
	}

	//----------------------------------------------------------------------------//
	// M�todos de desarrollo de XfdmAlgorithm                                     //
	//----------------------------------------------------------------------------//

	/**
	 * Obtiene un duplicado del objeto
	 */
	public Object clone() {
		return new XfdmFixedClustering(clustering, num_clusters, num_iterations,
				fuzziness, epsilon, learning);
	}

	/**
	 * Obtiene el nombre del algoritmo
	 */
	public String toString() {
		switch(clustering) {
			case HARD_CMEANS: return "Fixed Clustering (Hard-CMeans Algorithm)";
			case CMEANS: return "Fixed Clustering (CMeans Algorithm)";
			case GUSTAFSON_KESSEL:return "Fixed Clustering (Gustafson-Kessel Algorithm)";
			case GATH_GEVA: return "Fixed Clustering (Gath-Geva Algorithm)";
			default: return "";
		}
	}

	/**
	 * Indica si el algoritmo necesita que las variables de entrada tengan creadas
	 * las funciones de pertenencia. En caso contrario el algoritmo debe encargarse
	 * de dar contenido a los tipos de las variables de entrada.
	 */
	public boolean needInputMemFuncs() {
		return false;
	}
	
	/**
	 * Indica si el algoritmo necesita que las variables de salida tengan creadas
	 * las funciones de pertenencia. En caso contrario el algoritmo debe encargarse
	 * de dar contenido a los tipos de las variables de salida.
	 */
	public boolean needOutputMemFuncs() {
		return false;
	}

	/**
	 * Indica si el algoritmo necesita que la estructura del sistema est� creada.
	 * Este requisito es propio de los algoritmo que contienen alguna etapa de
	 * optimizaci�n param�trica.
	 * @return
	 */
	public boolean needSystemStructure() {
		return learning;
	}
	
	/**
	 * Indica si el algoritmo es v�lido para problemas de clasificaci�n
	 * @return
	 */
	public boolean supportsClassification() {
		return true;
	}
	
	/**
	 * Indica si el algoritmo es v�lido para problemas de regresi�n
	 * @return
	 */
	public boolean supportsRegression() {
		return true;
	}
	
	/**
	 * Representaci�n en el fichero de configuraci�n
	 */
	public String toXML() {
		String eol = System.getProperty("line.separator", "\n");
		String lrn = (learning? "1": "0");
		String name = "";
		switch(clustering) {
			case HARD_CMEANS: name = "HardCMeans,"; break;
			case CMEANS: name = "CMeans,"; break;
			case GUSTAFSON_KESSEL: name = "GustafsonKessel,"; break;
			case GATH_GEVA: name = "GathGeva,"; break;
		}

		String code = "\t\t<algorithm name=\""+name+"\" >"+eol;
		code += "\t\t\t<param name=\"clusters\" value=\""+num_clusters+"\" ></param>"+eol;
		code += "\t\t\t<param name=\"iterations\" value=\""+num_iterations+"\" ></param>"+eol;
		code += "\t\t\t<param name=\"fuzziness\" value=\""+fuzziness+"\" ></param>"+eol;
		code += "\t\t\t<param name=\"epsilon\" value=\""+epsilon+"\" ></param>"+eol;
		code += "\t\t\t<param name=\"learning\" value=\""+lrn+"\" ></param>"+eol;
		code +=	"\t\t</algorithm>";
		return code;
	}

	/**
	 * Obtiene el tipo de base de conocimiento que genera el algoritmo
	 */
	public int getKnowledgeBaseKind() {
		return KnowledgeBase.RULE_SET;
	}

	/**
	 * Genera el contenido de la base de conocimiento a partir de los datos
	 */
	public void extractKnowlegde(KnowledgeBase base, XfdmConfig config) {
		Variable[] ivar = base.getInputs();
		Variable[] ovar = base.getOutputs();
		this.width = ivar.length + ovar.length;
		this.point = createPointsFromPatterns(config.getDataSet(),ivar.length);
		this.cluster = createClusters(base);
		this.degree = new double[point.length][cluster.length];
		this.old_degree = new double[point.length][cluster.length];

		clustering();

		createContent(base,config.getSystemStyle().getDefuzMethod());

		if(learning) learning(base,config);
	}

	//----------------------------------------------------------------------------//
	//                             M�TODOS PRIVADOS                               //
	//----------------------------------------------------------------------------//

	//----------------------------------------------------------------------------//
	// M�todos que desarrollan el algoritmo                                       //
	//----------------------------------------------------------------------------//

	/**
	 * Aplica el algoritmo de clustering elegido
	 */
	private void clustering() {
		switch(clustering) {
			case HARD_CMEANS:      HardCMeans(); break;
			case CMEANS:           CMeans(); break;
			case GUSTAFSON_KESSEL: GustafsonKessel(); break;
			case GATH_GEVA:        GathGeva(); break;
		}
	}

	/**
	 * Obtiene el conjunto normalizado de patrones
	 */
	private double[][] createPointsFromPatterns(XfdsDataSet dataset, int numinputs) {
		int length = dataset.input.length;
		double point[][] = new double[length][width];
		for(int i=0; i<length; i++) for(int j=0; j<width; j++) {
			if(j<numinputs) {
				point[i][j] = dataset.input[i][j];
			} else {
				point[i][j] = dataset.output[i][j-numinputs];
			}
		}
		return point;
	}

	/**
	 * Obtiene una representacion inicial aleatoria de los clusters 
	 */
	private double[][] createClusters(KnowledgeBase base) {
		Variable[] ivar = base.getInputs();
		Variable[] ovar = base.getOutputs();
		double cl[][] = new double[num_clusters][width];
		for(int j=0; j<ivar.length; j++) {
			double min = ivar[j].getType().getUniverse().min();
			double max = ivar[j].getType().getUniverse().max();
			for(int i=0; i<num_clusters; i++) {
				cl[i][j] = min + Math.random()*(max-min);
			}
		}
		for(int j=0; j<ovar.length; j++) {
			double min = ovar[j].getType().getUniverse().min();
			double max = ovar[j].getType().getUniverse().max();
			for(int i=0; i<num_clusters; i++) {
				cl[i][ivar.length+j] = min + Math.random()*(max-min);
			}
		}
		return cl;
	}

	/**
	 * Intercambia old_degree y degree
	 */
	private void setOldDegree() {
		double[][] aux;
		aux = old_degree;
		old_degree = degree;
		degree = aux;
	}

	/**
	 * Calcula la variaci�n de clusters entre dos iteraciones
	 */
	private double computeVariation() {
		double max = 0;
		for(int i=0; i<degree.length; i++) for(int j=0; j<degree[i].length; j++) {
			double diff = degree[i][j] - old_degree[i][j];
			if(diff < 0) diff = -diff;
			if(diff > max) max = diff;
		}
		return max;
	}

	/**
	 * Calcula la distancia euclidea entre dos puntos (al cuadrado)
	 */
	private double distance(double[] x, double[] y) {
		double dist = 0;
		for(int i=0; i<x.length; i++) dist += (x[i]-y[i])*(x[i]-y[i]);
		return dist;
	}

	//----------------------------------------------------------------------------//
	// M�todos que desarrollan el algoritmo HardCMeans                            //
	//----------------------------------------------------------------------------//

	/**
	 * Algoritmo HardCMeans
	 */
	private void HardCMeans() {
		double var = epsilon+1;
		for(int iter=0; iter<num_iterations && (epsilon<0 || var>epsilon); iter++) {
			setOldDegree();
			HardCMeansUpdateDegree();
			HardCMeansUpdateClusters();
			var = computeVariation();
		}
	}

	/**
	 * Actualiza el grado de pertenencia de los puntos a los clusters
	 */
	private void HardCMeansUpdateDegree() {
		for(int i=0; i<point.length; i++) {
			double min = distance(point[i],cluster[0]);
			int index = 0;
			for(int j=1; j<cluster.length; j++) {
				double dist = distance(point[i],cluster[j]);
				if(dist < min) { min = dist; index = j; }
			}
			for(int j=0; j<cluster.length; j++) {
				degree[i][j] = (j==index? 1.0 : 0.0);
			}
		}
	}

	/**
	 * Actualiza el valor de los clusters
	 */
	private void HardCMeansUpdateClusters() {
		for(int i=0; i<cluster.length; i++) {
			for(int j=0; j<cluster[i].length; j++) cluster[i][j] = 0.0;
			int sum = 0;
			for(int k=0; k<degree.length; k++) if(degree[k][i] == 1.0) {
				sum++;
				for(int j=0; j<cluster[i].length; j++) cluster[i][j] += point[k][j];
			} 
			if(sum != 0) for(int j=0; j<cluster[i].length; j++) {
				cluster[i][j] = cluster[i][j]/sum;
			}
		}
	}

	//----------------------------------------------------------------------------//
	//	 Metodos que desarrollan el algoritmo CMeans		//
	//----------------------------------------------------------------------------//

	/**
	 * Algoritmo CMeans	
	 */
	private void CMeans() {
		double var = epsilon+1;
		for(int iter=0; iter<num_iterations && (epsilon<0 || var>epsilon); iter++) {
			setOldDegree();
			CMeansUpdateDegree();
			updateClusters();
			var = computeVariation();
		}
	}

	/**
	 * Actualiza el grado de pertenencia de los puntos a los clusters
	 */
	private void CMeansUpdateDegree() {
		double dist[] = new double[cluster.length];
		for(int i=0; i<point.length; i++) {
			for(int j=0; j<cluster.length; j++) dist[j] = distance(point[i],cluster[j]);
			updateDegree(i,dist);
		}
	}

	//----------------------------------------------------------------------------//
	// M�todos que desarrollan el algoritmo de Gustafson-Kessel                   //
	//----------------------------------------------------------------------------//

	/**
	 * Algoritmo Gustafson-Kessel
	 */
	private void GustafsonKessel() {
		double[][][] covar = new double[cluster.length][width][width];
		double[][][] inverse = new double[cluster.length][width][width];
		double[] determ = new double[cluster.length];
		CMeansUpdateDegree();

		double var = epsilon+1;
		for(int iter=0; iter<num_iterations && (epsilon<0 || var>epsilon); iter++) {
			updateClusters();
			updateCovariance(covar);
			updateDeterminant(covar, determ);
			updateInverse(covar, inverse);
			setOldDegree();
			GustafsonKesselUpdateDegree(determ, inverse);
			var = computeVariation();
		}
	}

	/**
	 * Calcula la distancia segun el algoritmo de GustafsonKessel
	 */
	private double distanceGK(double[] x,double[] y,double determ,double[][] inv) {
		double alpha = Math.pow(determ,1/width);
		double dist = 0;
		for(int i=0; i<width; i++) for(int j=0; j<width; j++) {
			dist += (x[i]-y[i])*inv[i][j]*(x[j]-y[j]);
		}
		return alpha*dist;
	}

	/**
	 * Actualiza el grado de pertenencia de los puntos a los clusters
	 */
	private void GustafsonKesselUpdateDegree(double[] determ,double[][][] inverse){
		double dist[] = new double[cluster.length];

		for(int i=0; i<point.length; i++) {
			for(int j=0; j<cluster.length; j++) {
				dist[j] = distanceGK(point[i],cluster[j],determ[j],inverse[j]);
			}
			updateDegree(i,dist);
		}
	}

	//----------------------------------------------------------------------------//
	// M�todos que desarrollan el algoritmo de Gath-Geva                          //
	//----------------------------------------------------------------------------//

	/**
	 * Algoritmo Gath-Geva
	 */
	private void GathGeva() {
		double[][][] covar = new double[cluster.length][width][width];
		double[][][] inverse = new double[cluster.length][width][width];
		double[] determ = new double[cluster.length];
		double[] rho = new double[cluster.length];
		CMeansUpdateDegree();

		double var = epsilon+1;
		for(int iter=0; iter<num_iterations && (epsilon<0 || var>epsilon); iter++) {
			/**/System.out.println("ITERACION "+iter);
			updateClusters();
			/**/debug("CLUSTERS",cluster);
			updateCovariance(covar);
			/**/debug("COVARIANZA",covar);
			updateDeterminant(covar, determ);
			/**/debug("DETERMINANTE",determ);
			updateInverse(covar, inverse);
			/**/debug("INVERSA",inverse);
			updateRho(rho);
			/**/debug("RHO",rho);
			setOldDegree();
			GathGevaUpdateDegree(rho, determ, inverse);
			/**/debug("PERTENENCIA",degree);
			var = computeVariation();
		}
	}

	/**
	 * Debug...
	 */
	private void debug(String matrix, double[][] cl) {
		System.out.println("");
		System.out.println(matrix);
		for(int i=0; i<cl.length; i++) {
			for(int j=0; j<cl[i].length; j++) System.out.print(""+cl[i][j]+"  ");
			System.out.println("");
		}
		System.out.println("");
	}

	/**
	 * Debug...
	 */
	private void debug(String vector, double[] v) {
		System.out.println(vector);
		for(int i=0; i<v.length; i++) System.out.println(""+v[i]+"  ");
		System.out.println("");
	}

	/**
	 * Debug...
	 */
	private void debug(String matrix, double[][][] cl) {
		System.out.println("");
		System.out.println(matrix);
		for(int i=0; i<cl.length; i++) {
			System.out.println("Matriz "+i);
			for(int j=0; j<width; j++) {
				for(int k=0; k<width; k++) System.out.print(""+cl[i][j][k]+"  ");
				System.out.println("");
			}
			System.out.println("");
		}
		System.out.println("");
	}

	/**
	 * Actualiza el vector Rho
	 */
	private void updateRho(double[] rho) {
		for(int i=0; i<cluster.length; i++) {
			double sum = 0.0;
			for(int j=0; j<point.length; j++) sum += degree[j][i];
			rho[i] = sum/point.length;
		}
	}

	/**
	 * Calcula la distancia segun el algoritmo de GathGeva
	 */
	private double distanceGG(double[] x,double[] y,double alpha,double[][] inv) {
		double dist = 0;
		for(int i=0; i<width; i++) for(int j=0; j<width; j++) {
			dist += (x[i]-y[i])*inv[i][j]*(x[j]-y[j]);
		}
		return alpha*Math.exp(dist/2);
	}

	/**
	 * Actualiza el grado de pertenencia de los puntos a los clusters
	 */
	private void GathGevaUpdateDegree(double[] rho,double[] determ,
			double[][][] inverse) {
		double dist[] = new double[cluster.length];
		double alpha[] = new double[cluster.length];
		for(int j=0; j<cluster.length; j++) alpha[j] = Math.sqrt(determ[j])/rho[j];

		for(int i=0; i<point.length; i++) {
			for(int j=0; j<cluster.length; j++) {
				dist[j] = distanceGG(point[i],cluster[j],alpha[j],inverse[j]);
			}
			updateDegree(i,dist);
		}
	}

	//----------------------------------------------------------------------------//
	// M�todos auxiliares de c�lculo de matrices                                  //
	//----------------------------------------------------------------------------//

	/**
	 * Actualiza el valor de los clusters
	 */
	private void updateClusters() {
		for(int i=0; i<cluster.length; i++) {
			for(int j=0; j<cluster[i].length; j++) cluster[i][j] = 0.0;
			double sum = 0;
			for(int k=0; k<degree.length; k++) {
				double alpha = Math.pow(degree[k][i],fuzziness);
				sum += alpha;
				for(int j=0; j<cluster[i].length; j++) {
					cluster[i][j] += alpha*point[k][j];
				}
			} 
			if(sum != 0) for(int j=0; j<cluster[i].length; j++) {
				cluster[i][j] = cluster[i][j]/sum;
			}
		}
	}

	/**
	 * Calcula la covarianza
	 */
	private void updateCovariance(double[][][] covar) {
		double alpha = 0.0;
		double denom[][] = new double[width][width];
		for(int i=0; i<cluster.length; i++) {
			double num = 0.0;
			for(int l=0;l<width; l++) for(int m=0;m<width; m++) {
				denom[l][m] = 0.0;
			}
			for(int j=0; j<point.length; j++) {
				alpha = Math.pow(degree[j][i],fuzziness);
				for(int l=0;l<width; l++) for(int m=0;m<width; m++) {
					double beta = (point[j][l] - cluster[i][l])*(point[j][m] - cluster[i][m]);
					denom[l][m] += alpha*beta;
				}
				num += alpha;
			}
			for(int l=0;l<width; l++) for(int m=0;m<width; m++) {
				covar[i][l][m] = denom[l][m]/num;
			}
		}
	}

	/**
	 * Calcula los determinantes de las matrices de covarianza
	 */
	private void updateDeterminant(double[][][] covar, double[] determ) {
		for(int c=0; c<cluster.length; c++) {
			double[][] temp = new double[width][width];
			for(int i=0;i<width;i++) for(int j=0;j<width;j++) temp[i][j]=covar[c][i][j];

			for(int k=0; k<(temp.length-1); k++)
				for(int i=k+1; i<temp.length; i++)
					for(int j=k+1; j<temp.length; j++)
						temp[i][j]-=temp[i][k]*temp[k][j]/temp[k][k];
			double determinant = 1.0;
			for(int i=0; i<temp.length; i++) determinant*=temp[i][i];
			determ[c] = determinant;
		}
	}

	/**
	 * Calcula las matrices inversas de las covarianzas	
	 */
	private void updateInverse(double[][][] covar, double[][][] inverse) {
		for(int cl=0; cl<cluster.length; cl++) {
			double[][] temp = new double[width][width];
			for(int i=0;i<width;i++) for(int j=0;j<width;j++) temp[i][j]=covar[cl][i][j];

			double[][] b=new double[width][width];
			double[][] c=new double[width][width];
			for(int i=0; i<width; i++) for(int j=0;j<width;j++) { b[i][j]=0; c[i][j]=0;}
			for(int i=0; i<width; i++) b[i][i]=1.0;

			for(int k=0; k<width-1; k++) {
				for(int i=k+1; i<width; i++) {
					for(int s=0; s<width; s++) b[i][s]-=temp[i][k]*b[k][s]/temp[k][k];
					for(int j=k+1; j<width; j++) temp[i][j]-=temp[i][k]*temp[k][j]/temp[k][k];
				}
			}

			for(int s=0; s<width; s++) {
				c[width-1][s]=b[width-1][s]/temp[width-1][width-1];
				for(int i=width-2; i>=0; i--) {
					c[i][s]=b[i][s]/temp[i][i];
					for(int k=width-1; k>i; k--) c[i][s]-=temp[i][k]*c[k][s]/temp[i][i];
				}
			}

			for(int i=0;i<width;i++) for(int j=0;j<width;j++) inverse[cl][i][j]=c[i][j];
		}
	}

	/**
	 * Actualiza los grados de pertenencia de un punto teniendo en cuenta la distancia a los clusters 
	 */
	private void updateDegree(int i, double[] dist) {
		double pow = 2/(fuzziness-1);
		boolean crisp = false;
		for(int j=0; j<cluster.length; j++) if(dist[j] == 0) crisp = true;
		if(crisp) {
			for(int j=0; j<cluster.length; j++) {
				if(dist[j] == 0) degree[i][j] = 1.0;
				else degree[i][j] = 0.0;
			}
			return;
		}

		for(int j=0; j<cluster.length; j++) {
			if(Double.isNaN(dist[j])) { degree[i][j] = 0; continue; }
			double sum = 0;
			for(int k=0; k<cluster.length; k++) {
				if(!Double.isNaN(dist[k])) sum += Math.pow((dist[j]/dist[k]), pow);
			}
			degree[i][j] = 1/sum;
		}

		int NaNcount = 0;
		double sum = 0;
		for(int j=0; j<cluster.length; j++) {
			if(Double.isNaN(degree[i][j])) NaNcount++;
			else sum += degree[i][j];
		}
		if(NaNcount != 0) for(int j=0; j<cluster.length; j++) {
			if(Double.isNaN(degree[i][j])) degree[i][j] = (1-sum)/NaNcount;
		}

	}

	//----------------------------------------------------------------------------//
	// M�todos que generan el contenido del sistema                               //
	//----------------------------------------------------------------------------//

	/**
	 * Genera el contenido del sistema a partir de los clusters
	 */
	private void createContent(KnowledgeBase base, int defuz) {
		Variable[] ivar = base.getInputs();
		Variable[] ovar = base.getOutputs();
		for(int i=0; i<ivar.length; i++) createBells(ivar[i].getType(),i);
		for(int i=0; i<ovar.length; i++) {
			switch(defuz) {
				case XfdmSystemStyle.FUZZYMEAN:
					createSingletons(ovar[i].getType(),ivar.length + i);
					break;
				case XfdmSystemStyle.WEIGHTED:
					createBells(ovar[i].getType(),ivar.length + i);
					break;
				case XfdmSystemStyle.TAKAGI:
					createParametric(ovar[i].getType(),ivar.length + i, ivar.length);
					break;
			}
		}
		createRules(base);
	}

	/**
	 * Genera las reglas correspondientes a los clusters
	 */
	private void createRules(KnowledgeBase base) {
		Variable ivar[] = base.getInputs();
		Variable ovar[] = base.getOutputs();
		int is = Relation.IS;

		for(int i=0; i<cluster.length; i++) {
			LinguisticLabel pmf = ivar[0].getType().getAllMembershipFunctions()[i];
			Relation rel = Relation.create(is,null,null,ivar[0],pmf,base);
			for(int j=1; j<ivar.length; j++) {
				pmf = ivar[j].getType().getAllMembershipFunctions()[i];
				Relation nrel = Relation.create(is,null,null,ivar[j],pmf,base);
				rel = Relation.create(Relation.AND,rel,nrel,null,null,base);
			}
			Rule rule = new Rule(rel);
			for(int j=0; j<ovar.length; j++) {
				pmf = ovar[j].getType().getAllMembershipFunctions()[i];
				rule.add(new Conclusion(ovar[j],pmf,base));
			}
			((RuleSet) base).addRule(rule);
		}
	}


	//----------------------------------------------------------------------------//
	// M�todos que generan las funciones de pertenencia                           //
	//----------------------------------------------------------------------------//

	/**
	 * Crea un conjunto de singularidades
	 */
	private void createSingletons(Type type, int index) {
		Universe u = type.getUniverse();
		for(int cl=0; cl<cluster.length; cl++) {
			ParamMemFunc pmf = new xfuzzy.pkg.xfl.mfunc.singleton();
			pmf.set("mf"+cl, u);
			pmf.set( cluster[cl][index] );
			try { type.add(pmf); } catch(XflException e) {}
		}
	}

	/**
	 * Crea un conjunto de funciones parametricas
	 */
	private void createParametric(Type type, int index, int numinputs) {
		Universe u = type.getUniverse();
		double param[] = new double[numinputs+1];
		for(int i=0; i<cluster.length; i++) {
			ParamMemFunc pmf = new xfuzzy.pkg.xfl.mfunc.parametric();
			pmf.set("mf"+i, u);
			param[0] = cluster[i][index];
			try { pmf.set(param); type.add(pmf); } catch(XflException ex) {}
		}
	}

	/**
	 * Crea un conjunto de campanas
	 */
	private void createBells(Type type, int index) {
		Universe u = type.getUniverse();
		double param[] = new double[2];
		for(int cl=0; cl<cluster.length; cl++) {
			param[0] = cluster[cl][index];
			param[1] = getWidthForMF(type,index,cl);
			ParamMemFunc pmf = new xfuzzy.pkg.xfl.mfunc.bell();
			pmf.set("mf"+cl, u);
			try { pmf.set(param); type.add(pmf); } catch(XflException ex) {}
		}
	}

	/**
	 * Obtiene la anchura de una MF en funcion de los grados de pertenencia 
	 * de los puntos a cada cluster
	 */
	private double getWidthForMF(Type type, int var, int cl) {
		double min = type.getUniverse().min();
		double max = type.getUniverse().max();
		double center = cluster[cl][var];

		double dist1 = max-min;
		boolean test1 = false;
		for(int i=0; i<point.length; i++) {
			double mu = degree[i][cl];
			double x = point[i][var];
			if(mu>0.3) continue;
			test1 = true;
			double d = (x>center? x-center : center-x);
			if(d < dist1) dist1 = d;
		}

		double dist2 = 0;
		boolean test2 = false;
		for(int i=0; i<point.length; i++) {
			double mu = degree[i][cl];
			double x = point[i][var];
			if(mu<0.3) continue;
			test2 = true;
			double d = (x>center? x-center : center-x);
			if(d > dist2) dist2 = d;
		}

		double dist = 0;
		if(test1 & test2) dist = (dist1+dist2)/2;
		else if(test1) dist = dist1;
		else if(test2) dist = dist2;
		if(dist==0) return (max-min)/100;
		return dist;
	}

	/**
	 * Ajusta las salidas del sistema con el algoritmo de Marquardt-Levenberg
	 */
	private void learning(KnowledgeBase base, XfdmConfig config) {
		double param[] = { 0.1, 10.0, 0.1};
		int alg = XfslAlgorithm.MARQUARDT;
		int err = XfslErrorFunction.MEAN_SQUARE_ERROR;
		int end = XfslEndCondition.TRN_VAR;
		XfslConfig xfslconfig = new XfslConfig();
		try {
			xfslconfig.trainingfile = config.getDataSetFile();
			xfslconfig.algorithm = XfslAlgorithmFactory.createAlgorithm(alg,param);
			xfslconfig.errorfunction = new XfslErrorFunction(err);
			xfslconfig.endcondition.setLimit(end,0.001);
			xfslconfig.addSetting("ANY.ANY.ANY",false);
			Variable[] ovar = base.getOutputs();
			for(int i=0; i<ovar.length; i++) {
				xfslconfig.addSetting(ovar[i].getType().getName()+".ANY.ANY",true);
			}
			XfslThread lrnthread = new XfslThread(config.getSpecification(),xfslconfig);
			lrnthread.run();
		} catch (Exception ex) { }
	}

}

